

<!DOCTYPE html>
<html lang="en" data-default-color-scheme=auto>



<head>
  <meta charset="UTF-8">
  <link rel="apple-touch-icon" sizes="76x76" href="/img/fluid.png">
  <link rel="icon" href="/img/fluid.png">
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0, shrink-to-fit=no">
  <meta http-equiv="x-ua-compatible" content="ie=edge">
  
  <meta name="theme-color" content="#2f4154">
  <meta name="author" content="an-qiao">
  <meta name="keywords" content="">
  
    <meta name="description" content="自我介绍我做的是一个外包开发的项目，我们的团队大概有20号人左右，有项目经理，产品经理，前端（pc端、小程序、安卓、ios 一般是两三个），测试（1:3），我们后台开发人比较多一点，有十二三个，我们项目组的忙的时候也会从别的项目组外调（借人），项目开发阶段人比较多，项目持续了有一年左右的开发，后面的话一直进行功能的迭代，后面的话项目的工作量应该很小，后端有两三个人去维护这个项目就可以了，由于前端被">
<meta property="og:type" content="article">
<meta property="og:title" content="项目">
<meta property="og:url" content="http://example.com/2023/05/10/xm/index.html">
<meta property="og:site_name" content="Hexo">
<meta property="og:description" content="自我介绍我做的是一个外包开发的项目，我们的团队大概有20号人左右，有项目经理，产品经理，前端（pc端、小程序、安卓、ios 一般是两三个），测试（1:3），我们后台开发人比较多一点，有十二三个，我们项目组的忙的时候也会从别的项目组外调（借人），项目开发阶段人比较多，项目持续了有一年左右的开发，后面的话一直进行功能的迭代，后面的话项目的工作量应该很小，后端有两三个人去维护这个项目就可以了，由于前端被">
<meta property="og:locale" content="en_US">
<meta property="og:image" content="http://example.com/xm/%E8%AE%A2%E5%8D%95%E8%A1%A8.png">
<meta property="og:image" content="http://example.com/xm/%E5%95%86%E5%93%81%E8%A1%A8.png">
<meta property="og:image" content="http://example.com/xm/image-20230504115659410.png">
<meta property="og:image" content="http://example.com/xm/image-20230505092332904.png">
<meta property="og:image" content="http://example.com/xm/clip_image002.jpg">
<meta property="article:published_time" content="2023-05-10T01:50:18.000Z">
<meta property="article:modified_time" content="2023-05-10T01:55:42.378Z">
<meta property="article:author" content="An-qiao">
<meta property="article:tag" content="简历">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:image" content="http://example.com/xm/%E8%AE%A2%E5%8D%95%E8%A1%A8.png">
  
  
    <meta name="referrer" content="no-referrer-when-downgrade">
  
  
  <title>项目 - Hexo</title>

  <link  rel="stylesheet" href="https://lib.baomitu.com/twitter-bootstrap/4.6.1/css/bootstrap.min.css" />



  <link  rel="stylesheet" href="https://lib.baomitu.com/github-markdown-css/4.0.0/github-markdown.min.css" />

  <link  rel="stylesheet" href="https://lib.baomitu.com/hint.css/2.7.0/hint.min.css" />

  <link  rel="stylesheet" href="https://lib.baomitu.com/fancybox/3.5.7/jquery.fancybox.min.css" />



<!-- 主题依赖的图标库，不要自行修改 -->
<!-- Do not modify the link that theme dependent icons -->

<link rel="stylesheet" href="//at.alicdn.com/t/font_1749284_hj8rtnfg7um.css">



<link rel="stylesheet" href="//at.alicdn.com/t/font_1736178_lbnruvf0jn.css">


<link  rel="stylesheet" href="/css/main.css" />


  <link id="highlight-css" rel="stylesheet" href="/css/highlight.css" />
  
    <link id="highlight-css-dark" rel="stylesheet" href="/css/highlight-dark.css" />
  




  <script id="fluid-configs">
    var Fluid = window.Fluid || {};
    Fluid.ctx = Object.assign({}, Fluid.ctx)
    var CONFIG = {"hostname":"example.com","root":"/","version":"1.9.4","typing":{"enable":true,"typeSpeed":70,"cursorChar":"_","loop":false,"scope":[]},"anchorjs":{"enable":true,"element":"h1,h2,h3,h4,h5,h6","placement":"left","visible":"hover","icon":""},"progressbar":{"enable":true,"height_px":3,"color":"#29d","options":{"showSpinner":false,"trickleSpeed":100}},"code_language":{"enable":true,"default":"TEXT"},"copy_btn":true,"image_caption":{"enable":true},"image_zoom":{"enable":true,"img_url_replace":["",""]},"toc":{"enable":true,"placement":"left","headingSelector":"h1,h2,h3,h4,h5,h6","collapseDepth":0},"lazyload":{"enable":true,"loading_img":"/img/loading.gif","onlypost":false,"offset_factor":2},"web_analytics":{"enable":true,"follow_dnt":true,"baidu":null,"google":null,"gtag":null,"tencent":{"sid":null,"cid":null},"woyaola":null,"cnzz":null,"leancloud":{"app_id":null,"app_key":null,"server_url":null,"path":"window.location.pathname","ignore_local":false}},"search_path":"/local-search.xml"};

    if (CONFIG.web_analytics.follow_dnt) {
      var dntVal = navigator.doNotTrack || window.doNotTrack || navigator.msDoNotTrack;
      Fluid.ctx.dnt = dntVal && (dntVal.startsWith('1') || dntVal.startsWith('yes') || dntVal.startsWith('on'));
    }
  </script>
  <script  src="/js/utils.js" ></script>
  <script  src="/js/color-schema.js" ></script>
  

  

  

  

  

  

  

  



  
<meta name="generator" content="Hexo 6.3.0"></head>


<body>
  

  <header>
    

<div class="header-inner" style="height: 70vh;">
  <nav id="navbar" class="navbar fixed-top  navbar-expand-lg navbar-dark scrolling-navbar">
  <div class="container">
    <a class="navbar-brand" href="/">
      <strong>Fluid</strong>
    </a>

    <button id="navbar-toggler-btn" class="navbar-toggler" type="button" data-toggle="collapse"
            data-target="#navbarSupportedContent"
            aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
      <div class="animated-icon"><span></span><span></span><span></span></div>
    </button>

    <!-- Collapsible content -->
    <div class="collapse navbar-collapse" id="navbarSupportedContent">
      <ul class="navbar-nav ml-auto text-center">
        
          
          
          
          
            <li class="nav-item">
              <a class="nav-link" href="/">
                <i class="iconfont icon-home-fill"></i>
                <span>主页</span>
              </a>
            </li>
          
        
          
          
          
          
            <li class="nav-item">
              <a class="nav-link" href="/archives/">
                <i class="iconfont icon-archive-fill"></i>
                <span>归档</span>
              </a>
            </li>
          
        
          
          
          
          
            <li class="nav-item">
              <a class="nav-link" href="/categories/">
                <i class="iconfont icon-category-fill"></i>
                <span>类别</span>
              </a>
            </li>
          
        
          
          
          
          
            <li class="nav-item">
              <a class="nav-link" href="/tags/">
                <i class="iconfont icon-tags-fill"></i>
                <span>标签</span>
              </a>
            </li>
          
        
          
          
          
          
            <li class="nav-item">
              <a class="nav-link" href="/about/">
                <i class="iconfont icon-user-fill"></i>
                <span>关于</span>
              </a>
            </li>
          
        
        
          <li class="nav-item" id="search-btn">
            <a class="nav-link" target="_self" href="javascript:;" data-toggle="modal" data-target="#modalSearch" aria-label="Search">
              <i class="iconfont icon-search"></i>
            </a>
          </li>
          
        
        
          <li class="nav-item" id="color-toggle-btn">
            <a class="nav-link" target="_self" href="javascript:;" aria-label="Color Toggle">
              <i class="iconfont icon-dark" id="color-toggle-icon"></i>
            </a>
          </li>
        
      </ul>
    </div>
  </div>
</nav>

  

<div id="banner" class="banner" parallax=true
     style="background: url('/img/default.png') no-repeat center center; background-size: cover;">
  <div class="full-bg-img">
    <div class="mask flex-center" style="background-color: rgba(0, 0, 0, 0.3)">
      <div class="banner-text text-center fade-in-up">
        <div class="h2">
          
            <span id="subtitle" data-typed-text="项目"></span>
          
        </div>

        
          
  <div class="mt-3">
    
      <span class="post-meta mr-2">
        <i class="iconfont icon-author" aria-hidden="true"></i>
        an-qiao
      </span>
    
    
      <span class="post-meta">
        <i class="iconfont icon-date-fill" aria-hidden="true"></i>
        <time datetime="2023-05-10 09:50" pubdate>
          May 10, 2023 am
        </time>
      </span>
    
  </div>

  <div class="mt-1">
    
      <span class="post-meta mr-2">
        <i class="iconfont icon-chart"></i>
        
          24k words
        
      </span>
    

    
      <span class="post-meta mr-2">
        <i class="iconfont icon-clock-fill"></i>
        
        
        
          241 mins
        
      </span>
    

    
    
      
        <span id="leancloud-page-views-container" class="post-meta" style="display: none">
          <i class="iconfont icon-eye" aria-hidden="true"></i>
          <span id="leancloud-page-views"></span> views
        </span>
        
      
    
  </div>


        
      </div>

      
    </div>
  </div>
</div>

</div>

  </header>

  <main>
    
      

<div class="container-fluid nopadding-x">
  <div class="row nomargin-x">
    <div class="side-col d-none d-lg-block col-lg-2">
      
  <aside class="sidebar" style="padding-left: 2rem; margin-right: -1rem">
    <div id="toc">
  <p class="toc-header">
    <i class="iconfont icon-list"></i>
    <span>Table of Contents</span>
  </p>
  <div class="toc-body" id="toc-body"></div>
</div>



  </aside>


    </div>

    <div class="col-lg-8 nopadding-x-md">
      <div class="container nopadding-x-md" id="board-ctn">
        <div id="board">
          <article class="post-content mx-auto">
            <!-- SEO header -->
            <h1 style="display: none">项目</h1>
            
              <p class="note note-info">
                
                  
                    Last updated on 5 days ago
                  
                
              </p>
            
            
              <div class="markdown-body">
                
                <h1 id="自我介绍"><a href="#自我介绍" class="headerlink" title="自我介绍"></a>自我介绍</h1><p>我做的是一个外包开发的项目，我们的团队大概有20号人左右，有项目经理，产品经理，前端（pc端、小程序、安卓、ios 一般是两三个），测试（1:3），我们后台开发人比较多一点，有十二三个，我们项目组的忙的时候也会从别的项目组外调（借人），项目开发阶段人比较多，项目持续了有一年左右的开发，后面的话一直进行功能的迭代，后面的话项目的工作量应该很小，后端有两三个人去维护这个项目就可以了，由于前端被调走了，我也会做一些前段的工作，熟悉js，jquery，vue的使用，熟悉vue组件。</p>
<p>您好，我是王怡彤,来自河北邯郸,今年25岁,今天来咱们公司是应聘Java开发工程师的职位,我毕业后一直从Java后端开发的工作，工作了4年多，在工作期间，我接触过电商、保险等类型的项目,具备独立开发的能力，我掌握的后端技术栈有:</p>
<p>熟练掌握 Java 基础知识。  Spring，SpringMVC，SpringCloud，springboot，Mybatis，Mybatis-Plus 微服务等常用框架、SpringBoot 工具集  熟悉 SpringCloud 常用组件像：注册中心 Eureka、Nacos，负载均衡器， Ribbon，Feign，服务保护 Hystrix，网关 Gateway ，Api 管理 Swagger ， 服务链路 Zipkin 等   熟练掌握 MySql、Oracle 数据库，及 SQL 语句的编写，了解 SQL 优化   熟悉掌握 redis 非关系型数据库，熟练使用 redis 常用操作命令  熟悉使用 RabbitMQ 消息中间键  熟练使用 poi 与 easyExcel 数据报表   </p>
<p>以上是后端我所掌握的技术栈，然后前端的技术栈有： jQuery、js、Html、Element-UI、vue 、echars前端开发技术  熟练使用 Linux 操作系统，常⽤命令及基本的服务器配置</p>
<p>我最近负责的项目是一个专注于母婴，婴幼儿用品的服务，母婴，幼儿所需的用品大部分都可在这里买到的一个电商项目，我项目中主要负责1、 负责商品模块中的上架、展示、下架  2、 支付宝支付功能的实现  3、 负责提供订单、查询订单、修改订单状态、以及定时清理支付超时订单  	</p>
<p>项目使用前后端分离实现、前端整体采用Vue的相关技术， 后端主要使用SpringBoot + SpringCloud为主的微服务架构，大体架构是使用nginx反向代理网关 网关使用的是SpringCloudGateway, 然后注册中心 和 配置中心用的都是 alibaba的nacos,微服务中的配置都会统一存放在nacos中， 所有微服务提供的服务地址也会存储在nacos中 服务与服务之间使用Feign的Http客户端调用，另外Redis做页面缓存，使用MQ异步处理，限流使用的是sentinel脚本，数据方面：核心数据存到了mysql中 ， 我在项目中主要负责后台微服务接口开发，负责商品模块、订单模块的相关功能</p>
<p> <strong>富江债券管理系统              2022.03-2023.03</strong></p>
<p><strong>鑫护保险系统                   2021-04—2022-02</strong></p>
<p> <strong>小豪租赁系统                2020.07–2021.03</strong></p>
<p><strong>久久CRM平台               2019.08-2020.07</strong>  </p>
<h2 id="薪资构成"><a href="#薪资构成" class="headerlink" title="薪资构成"></a>薪资构成</h2><p>薪资构成的话主要就是基本工资,奖金,五险,餐补,交通补助这些的</p>
<h2 id="学习（非工作时间）"><a href="#学习（非工作时间）" class="headerlink" title="学习（非工作时间）"></a>学习（非工作时间）</h2><p>平时非工作时间会浏览一些公众号，csdn，哔哩哔哩的视频，学习一些东西，比如vue就是我自学的，redis集群，es集群，kafka集群都自己学习过，项目中也有在用，基本是项目中用什么就学什么</p>
<h2 id="离职原因"><a href="#离职原因" class="headerlink" title="离职原因"></a>离职原因</h2><p>一、我之前主要是外派的工作，项目结束，外包清场了，岗位还是蛮多的，最近的话北京这边我们公司没有外派岗了，公司迫不得已接了一些偏远地区的项目，而且是长期还没有做好心理准备（新疆\东北\南方），还是想在北京这边找一个工作，公司这边推荐的外派岗位也在面试，自己也在投简历找，希望能尽快找到工作。</p>
<p>二、我在的公司是一个外包公司，公司项目最近两年越来越少，跟甲方的合作也都出现了一些问题，主要是因为疫情还有现在的一个环境造成的，我们公司之前大概有两三百人，最近的话，缩减到一百人左右了，项目排期很多人都出现了空档，如果没有实际的项目开发的话，我们超过半个月就按50%发放薪资，属于没有办法才去找一个新的工作，从公司角度来说也是没有办法，如果我现在在公司只能去一些短期的项目组所以考虑找一个新的工作，或者大的项目。</p>
<h3 id="线上问题1"><a href="#线上问题1" class="headerlink" title="线上问题1"></a>线上问题1</h3><p>一般的问题，项目刚发布上线的时候问题提交多，一般都是配置的问题， 或者是空指针，业务数据不一致造成的，所以比较容易解决，还有比较常见的问题：服务调不通，或者是服务不可用这种情况一般是环境的问题，我有时候也会跟运维一块去看。</p>
<h3 id="线上问题2"><a href="#线上问题2" class="headerlink" title="线上问题2"></a><strong>线上问题2</strong></h3><p>生产环境有过接触，但是并不多，我们这有运维会给我们反馈一些问题（我们是外包、驻场开发的，一般线上问题都是甲方的人去解决的比较多）</p>
<p>我们有些项目是有elk这种日志系统，我们可以他来定位错误，他可以通过异常信息，接口地址，服务器ip，还有服务名称，时间区间来快速定位到错误，然后跟我们的控制台一样可以快速定位到哪行代码出的问题，能帮我们快速解决问题。</p>
<h1 id="人资问题"><a href="#人资问题" class="headerlink" title="人资问题"></a>人资问题</h1><h5 id="1-谈谈你们研发团队的组成是怎样的"><a href="#1-谈谈你们研发团队的组成是怎样的" class="headerlink" title="1. 谈谈你们研发团队的组成是怎样的"></a><strong>1.</strong> <strong>谈谈你们研发团队的组成是怎样的</strong></h5><p>  我们这个研发小组，一共16个人，后台java一共5个，前端两个，UI两个，测试两个     然后还有项目经理、架构师、产品、运维  其中：UI和前端是所有项目组公用  </p>
<h5 id="2-谈谈你们项目的开发流程"><a href="#2-谈谈你们项目的开发流程" class="headerlink" title="2. 谈谈你们项目的开发流程"></a><strong>2.</strong> <strong>谈谈你们项目的开发流程</strong></h5><p>回答样例：</p>
<p>   一般我们项目都是先立项，决定要做什么项目  产品会梳理需求，产生需求说明和原型  架构师根据原型设计架构、数据库  然后给我们分派任务，规划项目开发周期  具体开发时，我们现在都是按照前后端分离的模式来      前端和后端一起根据业务原型  梳理业务需求      根据业务需求定义接口文档      前后端并行开发      后端基于接口文档提供对应的接口实现, 单元测试  POSTMAN      前端基于接口文档生成mock数据,进行前端的开发,按照mock数据进行测试      功能完成后进行前后端接口联调        前端把访问地址改成后端服务的地址，进行真实数据的测试      如果联调有问题，谁的问题谁去改  在进行联调功能测试，测试完毕即可发布到测试服务器，由测试人员进行测试  到时就改bug 在测  bug搞定后上线  ‘</p>
<h5 id="3-接口文档是由谁来提供，什么格式"><a href="#3-接口文档是由谁来提供，什么格式" class="headerlink" title="3. 接口文档是由谁来提供，什么格式?"></a><strong>3.</strong> <strong>接口文档是由谁来提供，什么格式?</strong></h5><p>回答样例：</p>
<p>​    后端提供的，不过前期根据原型有和前端讨论过，后端引入swagger框架生成的接口文档  </p>
<p>请求路径  &#x2F;api&#x2F;v1&#x2F;channel&#x2F;list </p>
<p>请求方式:  get post delete put  </p>
<p>请求参数:  @RequestBody  @PathVariable @RequestParam    </p>
<p>响应内容:  ResponseResult:           </p>
<p>code  状态码  enum枚举维护状态码 0 成功             </p>
<p>message  提示信息         </p>
<p>  data  数据  </p>
<h5 id="4-项目数据库的设计，如果让你设计表该如何设计"><a href="#4-项目数据库的设计，如果让你设计表该如何设计" class="headerlink" title="4.项目数据库的设计，如果让你设计表该如何设计?"></a>4.项目数据库的设计，如果让你设计表该如何设计?</h5><p>回答样例：</p>
<p>  我们项目是根据不同的微服务分了不同的数据库，表不到100张， 我们的数据库主要是由架构师设计的，如果让我设计 我会根据原型梳理具体业务模块，根据业务模块间的关系梳理出不同表关系，然后在PowerDesigner上创建出对应的表  和 表字段  。 一些命名规则就根据公司的规范走，会注意满足数据库三范式，当然有时为了提高查询效率会创建对应的冗余字段，减少连接查询。字段选择合理的字段类型和范围，经常查询的字段需要考虑索引  </p>
<h5 id="5-项目代码是如何管理的？"><a href="#5-项目代码是如何管理的？" class="headerlink" title="5.项目代码是如何管理的？"></a>5.项目代码是如何管理的？</h5><p>思路分析：</p>
<p>  考察之前项目的代码如何管理，需要准备使用git的大体流程  clone指定分支代码:  git clone -b 指定分支 Git地址  </p>
<p>回答样例：</p>
<p>  我们之前代码都是统一git管理的，公司服务器部署了GitLab, 每个人都有账号密码，从git上clone dev分支代码到本地, 功能开发后会将代码先pull更新，在提交到git开发分支，如果要有冲突，解决冲突部分然后通过merge命令进行合并，  我们项目尽力在功能完成时会将dev分支 合并到测试分支，然后发布一版进行功能测试  </p>
<h5 id="6-你实际年龄为什么跟你身份证年龄差那么多？"><a href="#6-你实际年龄为什么跟你身份证年龄差那么多？" class="headerlink" title="6.你实际年龄为什么跟你身份证年龄差那么多？"></a>6.你实际年龄为什么跟你身份证年龄差那么多？</h5><p>是因为计划生育的原因，因为我还有一个哥哥，然后后面生我的时候，考虑这个问题，就让我跟爷<br>爷奶奶住，然后上户口就晚了。身体太弱，差点没了。。父母闹矛盾，无人要，前期跟着奶奶相依为命</p>
<h5 id="7-你的最大的缺点是什么？"><a href="#7-你的最大的缺点是什么？" class="headerlink" title="7.你的最大的缺点是什么？"></a>7.你的最大的缺点是什么？</h5><p>性格有点强势，做事讲原则不讲人情 ，性格比较直，对人太热情，太实在，掏心陶肺，容易受到伤害。</p>
<h5 id="8-教育背景？"><a href="#8-教育背景？" class="headerlink" title="8.教育背景？"></a>8.教育背景？</h5><p>1998年8月28出生 今年24，毕业于山西应用科技学院</p>
<p>学校在小店区北格镇榆古路东一号山西应用科技学院，属于信息工程学院这个院系，学校还有财经学院，管理学院，美术学院，音乐学院等，校长：宋兴航，</p>
<p>高等数学、计算机组成原理、操作系统、计算机网络、编译原理、计算机体系结构、数字逻辑与数字电路、数据库原理、数据结构与算法、电路与电子技术、软件工程、程序设计基础、高级语言程序设计、概率论与数理统计、电子技术基础等课程</p>
<p>数据结构与算法综合实验<br>数据结构<br>数据库原理与应用<br>计算机组成与体系结构<br>概率论与数理统计<br>线性代数<br>离散数学<br>高数<br>c语言程序设计</p>
<h5 id="9-你未来规划是什么？"><a href="#9-你未来规划是什么？" class="headerlink" title="9.你未来规划是什么？"></a>9.你未来规划是什么？</h5><p>上进心，稳定，有主见，争取在公司一直干下去</p>
<h5 id="10-你现在在哪住？希望上班通勤是多久？"><a href="#10-你现在在哪住？希望上班通勤是多久？" class="headerlink" title="10.你现在在哪住？希望上班通勤是多久？"></a>10.你现在在哪住？希望上班通勤是多久？</h5><p>海淀，通勤希望1个小时内。我面试从家到咱公司花了2小时，到时面试成功了会搬家，正好咱们<br>公司附近有一个同事也在这</p>
<h5 id="11-你现在是离职还是在职？上一家离职原因是什么？"><a href="#11-你现在是离职还是在职？上一家离职原因是什么？" class="headerlink" title="11. 你现在是离职还是在职？上一家离职原因是什么？"></a>11. 你现在是离职还是在职？上一家离职原因是什么？</h5><p>薪资。生活所迫（房贷、父母、车贷），倒闭、欠薪、公司地址的搬迁</p>
<h5 id="12-你对加班怎么看的？"><a href="#12-你对加班怎么看的？" class="headerlink" title="12.你对加班怎么看的？"></a>12.你对加班怎么看的？</h5><p>首先，我不怎么排斥加班，现在还年轻，有必要多花时间去累计经验，而且互联网这个行业发展很快，我也需要学习新东西跟上行业的脚步。所以如果需要，我会支持加班的。<br>少壮不努力老大徒伤悲</p>
<h5 id="13-你对我们公司还有什么其他想问的吗？"><a href="#13-你对我们公司还有什么其他想问的吗？" class="headerlink" title="13.你对我们公司还有什么其他想问的吗？"></a>13.你对我们公司还有什么其他想问的吗？</h5><p>项目，人员配备，未来的工作安排</p>
<h1 id="项目"><a href="#项目" class="headerlink" title="项目"></a>项目</h1><h2 id="乐购之家"><a href="#乐购之家" class="headerlink" title="乐购之家"></a>乐购之家</h2><p>项目使用前后端分离实现、分为PC管理端、小程序端，前端整体采用Vue的相关技术， 后端主要使用SpringBoot + SpringCloud为主的微服务架构，大体架构是使用nginx反向代理网关 网关使用的是SpringCloudGateway, 然后注册中心 和 配置中心用的都是 alibaba的nacos,微服务中的配置都会统一存放在nacos中， 所有微服务提供的服务地址也会存储在nacos中 服务与服务之间使用Feign的Http客户端调用，另外Redis做页面缓存，使用MQ异步处理，限流使用的是sentinel脚本，数据方面：核心数据存到了mysql中 ， 我在项目中主要负责后台微服务接口开发，负责商品模块、订单模块、广告模块、用户模块的相关功能</p>
<h3 id="处理高并发"><a href="#处理高并发" class="headerlink" title="处理高并发"></a>处理高并发</h3><figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs">1，高并发问题我们会从多个方面考虑，性能，服务器压力，数据安全的问题。<br>2，性能方面可以通过页面的静态化页面，缓存，提升响应速度。<br>3，服务器压力方面，可以对关键的节点做集群解决，把请求压力平摊到各个节点上，或者	通过mq异步处理。<br>4，对于数据的完整性和一致性等安全问题，可以通过MQ队列排队。<br>5，关键是要找到系统的性能瓶颈，针对性的解决，调优。<br></code></pre></td></tr></table></figure>

<h3 id="秒杀"><a href="#秒杀" class="headerlink" title="秒杀"></a>秒杀</h3><figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs">1，秒杀这块会涉及到一段时间内大量请求同时访问，所以这块对性能要求还是比较高的。<br>2，我们这块是通过redis结合rabbitMQ实现的。<br>商品上架的时候会把商品的数据同步到redis中，<br>同时会使用freemaker模板引擎生成商品的详情页面挂载到nginx下。<br>商品数据通过redis查询，这样的话提升查询的速度，同时减轻了数据库的压力。<br>3，抢购的时候，我们会在redis中进行商品的预下单，预减库存操作。<br>这块主要是为了限制用户的请求，只有符合条件的请求才会发送数据到MQ排队。<br>4，MQ消费端接收到消息， 进行最终的处理， 生成订单，减库存保存到数据库当中，同时通知用户抢购成功。<br></code></pre></td></tr></table></figure>

<h3 id="页面缓存"><a href="#页面缓存" class="headerlink" title="页面缓存"></a>页面缓存</h3><figure class="highlight cos"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs cos">采用了freemarker模板引擎，考虑到页面也是要有动态的变化的，所以我们采用xxl-<span class="hljs-keyword">job</span>定时器在每天晚上<span class="hljs-number">2</span>点钟的时候定时再次生成html静态页面<br></code></pre></td></tr></table></figure>

<h3 id="只有10件库存，一万请求全部进全部进入系统吗？怎么处理的？"><a href="#只有10件库存，一万请求全部进全部进入系统吗？怎么处理的？" class="headerlink" title="只有10件库存，一万请求全部进全部进入系统吗？怎么处理的？"></a>只有10件库存，一万请求全部进全部进入系统吗？怎么处理的？</h3><figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs">1，我们在向MQ发送消息之前会有预下单，与预减库存操作，目的就是为了限制请求。<br>2，验证库存是否大于０，下单是否重复，验证通过后才会发消息到MQ排队处理。<br></code></pre></td></tr></table></figure>

<h3 id="超卖"><a href="#超卖" class="headerlink" title="超卖"></a>超卖</h3><figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs">1， 使用redis作为分布式锁的，但是该锁并不是阻塞型的，没有排队机制，不会遵循先到先得的机制。相对来说在抢购的过程中不是很公平。<br>2，我们抢购是使用的redis+rabbitMQ的方式处理的，（预减库存）符合条件的请求向MQ发送消息，异步处理，将并行变成串行处理。<br></code></pre></td></tr></table></figure>

<h3 id="订单流程"><a href="#订单流程" class="headerlink" title="订单流程"></a>订单流程</h3><figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs">想要买的商品，会存在购物车列表中， 当前台用户 在购物车列表中选择了确定购买的商品， 点击了结算按钮会跳转到订单确认页面，在订单确认页面中 会要求用户 选择收货地址 、支付方式 确认后呢 有一个提交订单的按钮， 点击这个按钮 调用后台的创建订单方法 。 我们的项目中 订单这涉及 支付日志表、订单主表、订单详情表  创建订单功能就是像这三个表中添加信息， 刚创建的订单 状态为 ： 待支付 ， 在创建订单这 我们进行了 防止订单重复提交的操作， 另外在创建订单这对于一直未支付的订单 我们是需要进行订单失效处理的，这里我们没使用定时器，而是使用了 rabbitMQ 的延时消息实现的，因为感觉用定时器实现，那定时器的时间间隔 还有每一次的定时任务 都需要访问数据库感觉不太好，另外项目中本身也使用了rabbitMQ 所以把哪些订单到了指定时间的任务全部交给了rabbitMQ , 我们系统只处理 到时间的订单，查看他们是否还处于待支付的状态，如果是待支付 就把他们改成失效状态。<br></code></pre></td></tr></table></figure>

<h5 id="订单表"><a href="#订单表" class="headerlink" title="订单表"></a>订单表</h5><p><img src="/xm/%E8%AE%A2%E5%8D%95%E8%A1%A8.png" srcset="/img/loading.gif" lazyload alt="订单表"></p>
<h5 id="商品表"><a href="#商品表" class="headerlink" title="商品表"></a>商品表</h5><p><img src="/xm/%E5%95%86%E5%93%81%E8%A1%A8.png" srcset="/img/loading.gif" lazyload alt="商品表"></p>
<h4 id="订单模块话术："><a href="#订单模块话术：" class="headerlink" title="订单模块话术："></a><strong>订单模块话术：</strong></h4><p>在我们的项目中，我负责的是订单模块，订单模块主要业务是用户选择好想要购买的商品后，订单模块生成相应的订单，根据订单的信息生成支付链接，用户支付成功后在商品页面的购物车中添加相应的商品信息。本项目订单模块中订单分为正常订单、秒杀订单，支付方式有微信支付和支付宝支付。主要使用的技术有rabbitmq延迟消息队列设置订单超时取消时间，第三方接口使用微信和支付宝的统一下单接口、查询订单接口、关闭订单接口等。</p>
<p>当时业务需求是用户选中商品进行支付购买，<strong>用户点击购买商品后会先判断用户是否登录</strong>，只有登录用户才能进行购买，未登录则跳转到登陆页面。登录用户点击购买商品后我根据前端传递过来的商品id和从本地线程ThreadLocal[<a href="#_msocom_12">12]</a> 中获取的用户id通过openfeign分别进行用户模块和商品模块的远程调用获得商品信息和用户信息，并根据商品信息和用户信息生成预支付订单。</p>
<p>使用ThreadLocal主要是为了确保不同用户访问时获取的是自己的用户id，而ThreadLocal会为每个使用该变量的线程创建一个独立的副本，为每个线程提供一份独立的存储空间，具有线程隔离效果，这样就确保不同用户都是操作的自己线程内的 USRE_ID，各个线程之间互不影响。</p>
<p>订单状态有（0、未支付 1、已支付 2、订单超时关闭 3、取消订单 4、支付超时关闭 ）</p>
<p>生成订单时订单支付状态为0（未支付）的订单，订单id使用雪花算法生成，然后将订单信息返回给前端。</p>
<p>订单需要设置一个超时时间，<strong>超过时间则订单失效</strong>，这里我选择使用rabbitmq的延迟队列，生成订单时给rabbitmq发消息，rabbitmq延迟队列能够很好的实现订单的延迟处理，RabbitMQ的死信队列DeadQueue是由“死信交换机DLX”+“死信路由DLK”组成的，当然，可能还会有“TTL”，而DLX和DLK又可以绑定指向真正的队列RealQueue，这个队列RealQueue便是“消费者”真正监听的对象.而RabbitMQ的延迟&#x2F;延时队列DelayedQueue 则是由普通的队列来创建即可，唯一不同的地方在于其绑定的交换机的消息类型为 “x-delayed-message”。而两者的区别是死信队列是先进先出，而延迟&#x2F;延时队列则没有这个限制，且死信队列较为复杂，消息暂存在队列，而延迟&#x2F;延时队列消息暂存在交换机。为了简化rabbitmq的操作我使用了rabbitmq的延迟队列。发送延时消息，时间设置为30分钟，计算出30分钟对应的毫秒值后带上订单进行消息发送，订单服务设置监听，根据id查询出订单信息，如果订单状态不为1（已支付），则将订单状态修改为2（订单超时关闭）。</p>
<p><strong>用户在前端点击取消订单或者立即支付订单</strong>，取消订单则根据订单id将订单中的订单状态改为3（取消订单）。立即支付时则需要选择支付方式，我们订单模块中提供了两种当前市面上流行的支付方式，分别是<strong>微信支付</strong>和<strong>支付宝支付</strong>。</p>
<p>微信支付我使用的是微信的统一下单native接口，使用 HTTPClient来根据URL访问微信支付接口并且传递参数，参数是xml类型，我通过WXPayUtil将map类型以及微信签名转换为xml类型，其中需要我们必须传递的有公众账号id appid、交易类型NATIVE和商户号mch_id以及商品描述body和订单号及支付金额和notify_url支付成功回调的url。接口会返回给我们一个code码，SUCCESS为成功&#x2F;FAIL失败。失败时还会返回错误原因。SUCCESS时返回参数为map，我们主要获取二维码链接code_url，有效时间为2小时。</p>
<p>支付宝的接口，测试我是用的沙箱，沙箱测试通过 其实代码就没多大的问题，就可以把沙箱换成正式的支付宝环境即可，只需要替换下appid ，换下支付宝网关地址。传入的参数和微信大同小异，主要是微信和支付宝签名的具体内容不太一样，在yml配置文件里面写 url是网关 appid是商户唯一的id，primary私钥，publickey公钥，notifyUrl 异步回调地址。异步回调返回时code为10000代表调用成功，40004代表业务处理失败，会返回相应的错误信息。</p>
<p>然后引入google的core依赖，设置二维码内容，二维码编码方式，长，宽，参数（map格式）响应给前台。</p>
<p>异步回调的情况分三种，</p>
<p>（1）第一种异步回调成功，正常修改订单状态</p>
<p>（2）第二种异步回调失败，服务器应主动发起异步回调查询支付状态</p>
<p>（3）第三种用户打开支付二维码，不进行支付</p>
<p><strong>第一种异步回调成功，正常修改订单状态</strong></p>
<p>用户选择支付方式扫描二维码支付成功后，在相应客户端上（微信，支付宝）进行消息通知，同时同步调用订单系统中回调接口，回调接口主要是去调用相应的查询订单接口，传入appid，mch_id，商户订单号和签名。根据返回的结果，成功时根据map的trade_state交易状态判断是否成功支付（支付宝类同），并且判断支付金额和该订单的支付金额是否一致。成功则修改订单表中支付类型（1.微信支付 2.支付宝支付）为相应类型，修改订单状态为1（已支付），其他则提示支付失败，并根据支付类型（1.微信 2.支付宝）,调用相应的关闭未支付的交易的接口，主要传入公众账号id appid和商户号mch_id以及订单号。</p>
<p>但是我们的订单系统对于开启支付接口回调通知的内容一定要做签名验证，确保接口安全并校验通知的信息是否与订单的信息一致，防止数据泄露导致出现“假通知”，造成资金损失。</p>
<p>微信回调验签我是通过在微信商户平台设置api密钥，通过微信官方提供的签名算法生成api密钥。服务器收到回调通知后，将除sign以外的参数进行拼接，然后用微信商户提供的保存在自己服务器上的的私钥对其进行签名，将其与回调通知里的sign进行比对，如果一样，则服务器任务支付成功，执行支付后的业务逻辑。</p>
<p>而支付宝验签我主要是通过使用RSA签名验签，开放平台SDK封装了同步返回响应参数的验签方法，只需在创建DefaultAlipayClient对象进行初始化，设置请求网关(gateway)，应用id(app_id)，应用私钥(private_key)，编码格式(charset)，支付宝公钥(alipay_public_key)，签名类型(sign_type)即可，同步返回响应参数报文时会自动进行验签。</p>
<p><strong>第二种异步回调失败，服务器应主动发起同步回调查询支付状态</strong></p>
<p>用户支付，异步回调可能失败，不能完全确保我们订单服务接收到用户支付成功的消息，此时我们需要进行进行同步调用，主动去查询订单支付信息。</p>
<p>关于异步回调微信和支付宝官方提供了重试机制，当微信和支付宝的回调没有返回成功信息，那么它们会不断重试，</p>
<p>微信重试频率为15s&#x2F;15s&#x2F;30s&#x2F;3m&#x2F;10m&#x2F;20m&#x2F;30m&#x2F;30m&#x2F;30m&#x2F;60m&#x2F;3h&#x2F;3h&#x2F;3h&#x2F;6h&#x2F;6h - 总计24h4m，</p>
<p>而支付宝重试的频率是15s 3m 10m 30m 30m 1h 2h 6h 15h，我再结合前端设置定时3s调用一次我们的同步回调接口，当订单支付状态为不为0（未支付）时，关闭每隔3秒查询订单的设计。这样来确保用户支付成功后，我们订单系统能正确得到支付成功信息然后去修改订单状态。</p>
<p><strong>第三种用户打开支付二维码，不进行支付</strong></p>
<p>但是还有一种情况，用户不进行支付，短时间内用户不支付时，我们需要修改订单状态。我通过在生成二维码url时设置了一个定时任务，这里使用上面提到过的rabbitmq的死信队列实现，时间为5分钟，然后通过监听先去查询订单信息如果订单状态是1（已支付）就直接return，否则就调用相应的查询订单接口，查出trade_state交易状态不为SUCCESS则调用关闭支付的相应接口，主要传入 appid和mch_id以及订单号和签名，然后修改订单状态为 4（支付超时关闭订单）。</p>
<p>用户取消订单则调用关闭支付的相应接口，并将订单状态改为3（用户取消订单）。</p>
<p>用户在支付成功后，调用添加订单的方法，然后这里使用到了分布式事务。</p>
<p>分布式事务重点在于CAP理论以及BASE定理, Consistency（一致性）,Availability（可用性）,Partition tolerance （分区容错性）,分布式系统无法满足这三个指标,解决CAP理论也就是BASE定理有三个思想Basically Available （基本可用）：允许损失部分可用性，保证核心可用,Soft State（软状态）：在一定时间内，允许出现中间状态，比如临时的不一致状态,Eventually Consistent（最终一致性）：虽然无法保证强一致性，但是在软状态结束后，最终达到数据一致。</p>
<h6 id="当时使用分布式事务的时候，我想到了四种解决方案："><a href="#当时使用分布式事务的时候，我想到了四种解决方案：" class="headerlink" title="当时使用分布式事务的时候，我想到了四种解决方案："></a>当时使用分布式事务的时候，我想到了四种解决方案：</h6><p>​    2PC&#x2F;3PC：依赖于数据库，能够很好的提供强一致性和强事务性，但相对来说延迟比较高，比较适合传统的单体应用，在同一个方法中存在跨库操作的情况，不适合高并发和高性能要求的场景.</p>
<p>  TCC：适用于执行时间确定且较短，实时性要求高，对数据一致性要求高，比如交易、支付、账务, TCC 的 Try、Confirm 和 Cancel 操作功能要按具体业务来实现，业务耦合度较高，提高了开发成本.</p>
<p>  MQ 事务：适用于事务中参与方支持操作幂等，对一致性要求不高，业务上能容忍数据不一致到一个人工检查周期，事务涉及的参与方、参与环节较少，业务上有对账&#x2F;校验系统兜底,一次消息发送需要两次网络请求(half 消息 + commit&#x2F;rollback 消息) ,业务处理服务需要实现消息状态回查接口.</p>
<h6 id="Seata"><a href="#Seata" class="headerlink" title="Seata"></a>Seata</h6><p>Seata 提供了 AT、TCC、SAGA 和 XA 四种事务模式</p>
<p>​    XA模式：强一致性分阶段事务模式，牺牲了一定的可用性，无业务侵入</p>
<p>   TCC模式：最终一致的分阶段事务模式，有业务侵入</p>
<p>  AT模式：最终一致的分阶段事务模式，无业务侵入，也是Seata的默认模式</p>
<p>SAGA模式：长事务模式，有业务侵入。</p>
<p>而我选择使用seate的AT模式的原因是因为：它是一站式的分布式解决方案,对业务无侵入，减少技术架构上的微服务话所带来的分布式事务问题对业务的侵入,相对于其他方案来说性能高。但是使用AT模式的前提是基于支持本地 ACID 事务的关系型数据库（MySQL、Oracle）。Java 应用，通过 JDBC 访问数据库。</p>
<p>因此需要针对业务中涉及的各个数据库中，分别创建一个 <a href="mailto:UNDO_LOG（回滚日志）表，并且在需要使用分布式事务的方法上加入注解@GlobalTransactional,在每个微服务中放入registry.conf和file.conf文件,修改file.conf中的vgroup_mapping.服务群组名(对应yml中的my-service-group)">UNDO_LOG（回滚日志）表,在每个微服务中放入registry.conf和file.conf文件,修改file.conf中的vgroup_mapping.服务群组名(对应yml中的my-service-group)</a>在需要使用分布式事务的方法上加入注解@GlobalTransactional开启分布式事务，如果有一个地方执行失败seata就会自动帮我们回滚，保存成功则添加到数据库中。</p>
<p>考虑到随着项目的上线订单量越来越多,达到500w则会影响数据库性能，为了避免这个问题我使用Sharding-JDBC来做水平分表分库，使用分表的哈希分片策略来进行扩容，哈希分片可以较为均匀将数据分散在数据库中；我将订单库拆分为4个库编号为[0,3]，每个库4张表编号为[0,3]，使用orderId作为ShardingKey，在保存数据时先对库的序号进行计算（例如：db_index &#x3D; 100 % 4 &#x3D; 0），取模计算得到序号为0表示路由到db[0]；库确定了接着在db[0]进行取模表路由，最后就算出来了数据的存放地址。</p>
<p>水平分库后必须要考虑的是保持id的唯一，并且使id有序，能够提高查询速度。</p>
<p>当时想到有自增，uuid，利⽤ redis ⽣成 id ，雪花算法生成。最后选择了雪花算法生成id。原因是：</p>
<p>自增：两台数据库分别设置不同步⻓，⽣成不重复ID的策略来实现⾼可⽤。这种⽅式⽣成的 id 有序，但是需要独⽴部署数据库实例，成本⾼，还会有性能瓶颈。</p>
<p>UUID：不适合作为主键，因为太⻓了，并且⽆序不可读，查询效率低。</p>
<p>利⽤ redis ⽣成 id ：性能⽐较好，灵活⽅便，不依赖于数据库。但是，引⼊了新的组件造成系统更加复杂，可⽤性降低，编码更加复杂，增加了系统成本。</p>
<p>雪花算法：高并发分布式环境下生成不重复 id，每秒可生成百万个不重复 id，并且基于时间戳，生成的id是有序的，算法简单，在内存中进行，效率高。</p>
<p>其实大公司也有自己的方案解决id唯一，很多都是开源的，我了解的有美团的Leaf分布式ID⽣成系统。</p>
<h6 id="遇到的问题："><a href="#遇到的问题：" class="headerlink" title="遇到的问题："></a>遇到的问题：</h6><p>一、</p>
<p>配置完之后项目启动直接报错,后来查看了官方文档,MySQL7.0以下的版本UNDO_LOG回滚日志不需要context字段,而当时项目使用的是MySQL正好是5.7版本, UNDO_LOG必须加入context字段,添加该字段后问题解决.</p>
<p>二、</p>
<p>使用Seata处理分布式事务时，跨库数据库全部回滚成功，本地库中商品有效不能回滚，后来发现是不能回滚的自个字段是tinyint类型的且长度为1，因为当tinyint长度为1时，通过getobject从中获取数据的时候，得到的并不是一个int类型的值，而boolean类型值，这样当默认值大于0时，其实取到的这个值是true，而在更新这个字段的时候，我是从1把它改成了2，当seata在回滚的时候，发现值并未发生变化，因为1和2都是true，所以没有回滚，所以只需要将字段改为0(无效)、1（有效）或者将tinyint类型长度改为大于1，此问题解决</p>
<h6 id="订单的过期时间怎么设置"><a href="#订单的过期时间怎么设置" class="headerlink" title="订单的过期时间怎么设置"></a>订单的过期时间怎么设置</h6><figure class="highlight reasonml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs reasonml"><span class="hljs-comment">// 发送 死信队列</span><br>long hs = wmNews.get<span class="hljs-constructor">PublishTime()</span>.get<span class="hljs-constructor">Time()</span> - <span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">System</span>.</span></span>current<span class="hljs-constructor">TimeMillis()</span>;<br><span class="hljs-comment">// 消息</span><br>Message message = MessageBuilder<br>        .<span class="hljs-keyword">with</span><span class="hljs-constructor">Body(<span class="hljs-params">wmNews</span>.<span class="hljs-params">getId</span>()</span>.<span class="hljs-keyword">to</span><span class="hljs-constructor">String()</span>.get<span class="hljs-constructor">Bytes(StandardCharsets.UTF_8)</span>)<br>        .set<span class="hljs-constructor">Header(<span class="hljs-string">&quot;x-delay&quot;</span>,<span class="hljs-params">hs</span>)</span><br>        .build<span class="hljs-literal">()</span>;<br><span class="hljs-comment">// 发送消息</span><br>rabbitTemplate.convert<span class="hljs-constructor">AndSend(<span class="hljs-string">&quot;cjexchange&quot;</span>, <span class="hljs-string">&quot;cj&quot;</span>, <span class="hljs-params">message</span>)</span>;<br>return;<br></code></pre></td></tr></table></figure>

<h6 id="说一下ThreadLocal，和Synchronized的区别"><a href="#说一下ThreadLocal，和Synchronized的区别" class="headerlink" title="说一下ThreadLocal，和Synchronized的区别"></a><strong>说一下<strong><strong>ThreadLocal</strong></strong>，和<strong><strong>Synchronized</strong></strong>的区别</strong></h6><p>ThreadLocal叫做<strong>线程变量</strong>，意思是ThreadLocal中<strong>填充的变量</strong>属于<em>当前线程</em>，该变量对其他线程而言是隔离的，也就是说该变量是当前线程独有的变量。ThreadLocal为变量在每个线程中都创建了一个副本，那么每个线程可以访问自己内部的副本变量。</p>
<h6 id="区别"><a href="#区别" class="headerlink" title="区别"></a>区别</h6><p>1、Synchronized用于线程间的数据共享，而ThreadLocal则用于线程间的数据隔离。</p>
<p>2、Synchronized是利用锁的机制，使变量或代码块在某一时该只能被一个线程访问。而ThreadLocal为每一个线程都提供了变量的副本，使得每个线程在某一时间访问到的并不是同一个对象，这样就隔离了多个线程对数据的数据共享。而Synchronized却正好相反，它用于在多个线程间通信时能够获得数据共享</p>
<h4 id="秒杀-1"><a href="#秒杀-1" class="headerlink" title="秒杀"></a>秒杀</h4><p>当时负责了一个秒杀模块，公司开会的时候就考虑到了两种方式的秒杀一种是像双十一有很大并发的,另一种是每天都有的促销型的秒杀,我们就选择的就是促销型秒杀对商品进行秒杀，</p>
<p>给每个要秒杀的商品设置好默认的优惠价, 每天分为五个时间段，定时传入需要秒杀商品这里就是两种，这里的秒杀过程就是有两种，一种就是我们自动完成一个时间段里有五个秒杀的商品,用户可以点击查看每个时间段要秒杀的商品，另一个是管理员设置的，管理员呢设置的需要进行秒杀的商品每场不能超过10个，进行秒杀，秒杀呢大致分为了三步 秒杀前主要是要做缓存预热 秒杀时呢主要就是防止超卖 秒杀结束后支付问题，为了避免秒杀过程中频繁操作数据库，这里我们将用户与商品的关系、商品剩余数量，秒杀成功信息均保存在red is中</p>
<p>在秒杀开始前呢，我们用到了四张表 商品表,秒杀商品表,订单表以及秒杀订单表， 通过定时器将每场需要秒杀的商品信息从mysql里提前缓存到 redis 中，这么秒杀开始时则直接从 redis中读取，也就是缓存预热,通过springtask 每过五分钟定时查询要参加秒杀的商品,查询到要秒杀的商品存入redis中，然后用hash数据结构进行存储,大key是secskill 小key是商品id value 是商品信息,首页的秒杀的展示，根据对应的时间进行获取,然后响应到页面。开始之前有个活动倒计时，时间到了则会放开秒杀的权限，并生成一个验证码展示在前面页面，并把验证结果存在redis中，这里利用redis有过期时间的特性，也给验证码的缓存加了个过期时间。这里的redis缓存用的是redis的string类型。在秒杀之前先要填一个<strong>验证码</strong>，点击秒杀按钮时，先发送ajax请求到后台获取真实的秒杀地址，这里秒杀地址是隐藏的，目的是<strong>防止有人恶意刷秒杀接口</strong> </p>
<p>秒杀进行时,用户进入秒杀的时候,就会将对应时间的秒杀的商品从reids缓存里查询出来，在页面对客户进行展示,当用户去点击购买按钮的的时候,就会跳转到秒杀的详情页,进入详情页之后，用户可以点击下单，当用户点击下单我们先判断用户有没有登录，如果没有登陆让用户去认证服务那块去进行登录，登陆成功之后就进入秒杀的微服务,查看是不是存在未支付的秒杀订单如果有的话就让用户先去进行支付, 然后用redis 队列 list方式（一个商品就占一个队列，队列里存放商品的id）,当用户进行购买下单的时候就去创建的队列里取商品id(用redis的rlop),取一个id商品就会少一个，如果用户取不到，就证明商品没有了，然后提示商品已卖完，在排队的时候我们有三个状态（1在排队，2抢购到了，3待支付）这个状态,还是用的redis设置的string类型的保存的就是用户的id，和状态），判断是否排队上限如果上限给用户提示活动火爆请稍等,如果排队没有上限就配合未支付的秒杀订单进行异步下单，创建订单呢当时我考虑了两种方式一种是多线程一种是线程池 [<a href="#_msocom_13">13]</a> 我采用了线程池的方式应为线程池[<a href="#_msocom_14">14]</a> （执行完之后线程不会收回而是放在了队列里进行等待），创建一个CachedThreadPool 类型线程池的类(配置了一些核心的参数),设置成单例模式,用线程池主要就是异步请求的问题，排队是redis用队列进行实现 redis里的list本就是一个队列（我最初的办法是加锁，使用redission的分布式锁。后来发现，这种锁性能较差，然后我将业务逻辑改成将所有的商品数量依次添加到队列中，这里使用redis的list数据结构，当秒杀成功后直接从队列中取出一个数，这样就不会出现超卖问题，而且性能还高），每次从队列中获取数据，分别获取用户id和订单商品编号以及商品秒杀时间段，进行下单操作。然后读取排队下单状态，下单成功之后，<strong>redis****的操作</strong>的方法decr，将对应商品的库存减1，此时数据库时的库存还没有减,正确地预减库存后，然后就要操作数据库了，用<strong>异步方式</strong>处理。对于每一条<strong>秒杀请求存入消息队列RabbitMQ</strong>中，消息体中要包含<strong>哪个用户秒杀<strong><strong>的</strong></strong>哪个<strong><strong>商品</strong></strong>的信息</strong>，这里是封装了一个消息体类，这样一个秒杀请求就进入了消息队列，一个秒杀请求还没有完成，真正的秒杀请求的完成得要持久化到数据库，生成订单，减了数据库的库存,把排队的状态改为待支付</p>
<p>秒杀之后，还没支付时，有用redis创建了个队列存放的抢单信息，这时候mq发送一个延时队列的消息，这是队列里携带抢单信息，延时设置了10分钟，同时会跳转到支付的页面 支付页面显示订单编号和订单的金额，这时秒杀订单编号创建预支付信息，支付页面会根据用户id查看用户秒杀订单，并根据用户秒杀订单编号创建预支付信息并获取二维码信息，支付二维码的支付地址由订单微服务调用微信服务获取支付地址。展示给用户看，此时页面每3秒查询一次支付状态</p>
<p>如果支付成功会 删除redis的抢单队列相关数据，删除订单信息，删除排队信息，同时把数据同步到mysql 里</p>
<p>如果支付失败 支付失败分了几种情况</p>
<p> 第一种用户不支付超时了就用rabbitmq 里的死信队列 设置为5分钟 进行轮询操作 先是收到了延迟消息去查看微信的支付查看订单的状态 如果订单显示已经支付，就进行数据补偿（更改订单的状态并且记录日志），如果没有支付就关闭订单，进行库存回滚<br>   第二种失败了的话，订单也关闭了 就回退库存这是还得判断秒杀的时间结束没结束这是就得考虑秒杀时间是不是快结束了，这里呢当时做了个判断看秒杀结束时间如果超过五分钟了就回退库存，找到订单信息 取走，然后找到队列放到队列里，在给其他排队的用户进行抢购，如果没有超过五分钟,就等秒杀结束之后在同步</p>
<h6 id="问题："><a href="#问题：" class="headerlink" title="问题："></a>问题：</h6><p>超卖问题：</p>
<p>超卖问题的出现原因库存有可能扣到为负数，无法锁定最后的库存</p>
<p>如果从外面select一次库存，再查找又怕出现主从不一致问题</p>
<h6 id="并发问题"><a href="#并发问题" class="headerlink" title="并发问题"></a>并发问题</h6><p>我最初的办法是加锁，使用redission的分布式锁。后来发现，这种锁性能较差，然后我将业务逻辑改成将所有的商品数量依次添加到队列中，这里使用redis的list数据结构，当秒杀成功后直接从队列中取出一个数，这样就不会出现超卖问题，而且性能还高</p>
<h6 id="消息丢失的问题："><a href="#消息丢失的问题：" class="headerlink" title="消息丢失的问题："></a>消息丢失的问题：</h6><p> 由于我这个秒杀是用的reids队列进行了存取,不会涉及到消息丢失，但是rabbitMq</p>
<p>的消息丢失我知道可能有三个地方出现消息丢失的问题 </p>
<p>生产者消息丢失出现的原因</p>
<p> 生产者将数据发送到RabbitMQ的时候，可能因为网络问题导致数据没到达RabbitMQ Server。</p>
<p>transaction机制就是说:发送消息前，开启事务(channel.txSelect()),然后发送消息，如果发送过程中出现什么异常，事务就会回滚(channel.txRollback()),如果发送成功则提交事务</p>
<p>(channel.txCommit())。然而，这种方式有个缺点:吞吐量下降;</p>
<p>confirm模式用的居多︰一旦channel（查no）进入confirm模式，所有在该信道上发布的消息都将会被指派一个唯一的ID(从1开始)，一旦消息被投递到所有匹配的队列之后;</p>
<p>rabbitMQ就会发送一个ACK给生产者（包含消息的唯一ID)，这就使得生产者知道消息已经正确到达目的队列了;</p>
<p>如果rabbitMQ没能处理该消息，则会发送一个Nack消息给你，你可以进行重试操作</p>
<p>消费者消息丢失                                          </p>
<h6 id="消息队列丢失："><a href="#消息队列丢失：" class="headerlink" title="消息队列丢失："></a>消息队列丢失：</h6><p>消费者丢数据一般是因为采用了自动确认消息模式，改为手动确认消息即可!消费者在收到消息之后，处理消息之前，会自动回复RabbitMQ已收到消息;</p>
<p>如果这时处理消息失败，就会丢失该消息;</p>
<p>解决方案:处理消息成功后，手动回复确认消息。</p>
<p>消息持久化。</p>
<p>处理消息队列丢数据的情况，一般是开启持久化磁盘的配置。</p>
<p>这个持久化配置可以和confirm机制配合使用，你可以在消息持久化磁盘后，再给生产者发送一个Ack信号。</p>
<p>这样，如果消息持久化磁盘之前,rabbitMQ阵亡了，那么生产者收不到Ack信号，生产者会自动重发。那么如何持久化呢?</p>
<p>这里顺便说一下吧，其实也很容易，就下面两步</p>
<p>\1.   将queue的持久化标识durable设置为true,则代表是一个持久的队列</p>
<p>\2.   发送消息的时候将deliveryMode&#x3D;2</p>
<p>这样设置以后，即使rabbitMQ挂了，重启后也能恢复数据</p>
<h6 id="轮询："><a href="#轮询：" class="headerlink" title="轮询："></a>轮询：</h6><p>轮询是按照某种算法进行顺序触发，轮询时会保存<strong>当前执行后的索引</strong>，以便于下次执行时可以拿到<strong>开始索引位置</strong>，以达到负载均衡的目的。</p>
<h6 id="其他轮询规则和算法"><a href="#其他轮询规则和算法" class="headerlink" title="其他轮询规则和算法"></a>其他轮询规则和算法</h6><p>​    包括 <strong>定时轮询</strong>、<strong>加权轮询</strong>、<strong>随机轮询</strong>、*<em>权重轮询</em>***我采用的就是定时轮询</p>
<h6 id="什么是隐藏地址："><a href="#什么是隐藏地址：" class="headerlink" title="** 什么是隐藏地址："></a>** <strong>什么是隐藏地址：</strong></h6><p>请求地址中加一段随机字符串，这段字符串是变化的，因此秒杀请求地址是动态的这里呢（如何获取真实地址）后台先访问redis，验证一下这个验证码有没有过期以及这个verifyCode是不是正确，验证码验证通过后，先删除这个验证码缓存，然后生成真实地址，这个真实地址随机字符串由uuid以及md5（通过两次 MD5 加密提高数据校验的安全性。第一次 MD5 是防止用户的明文密码在网络上传输，第二次 MD5 是防止网上相关的 MD5 解密反查。</p>
<h6 id="秒杀商品的并发："><a href="#秒杀商品的并发：" class="headerlink" title="秒杀商品的并发："></a>秒杀商品的并发：</h6><p>Jmeter 进行测试</p>
<p>1000商品 100 </p>
<h6 id="Redis内存不够怎么办-："><a href="#Redis内存不够怎么办-：" class="headerlink" title="Redis内存不够怎么办 ："></a>Redis内存不够怎么办 ：</h6><p>可以搭建集群 进行分片 可能会出现脑列问题</p>
<h6 id="还有可以用内存的淘汰策略-这个策略呢有八种"><a href="#还有可以用内存的淘汰策略-这个策略呢有八种" class="headerlink" title="还有可以用内存的淘汰策略 这个策略呢有八种"></a>还有可以用内存的淘汰策略 这个策略呢有八种</h6><p>noeviction：直接返回错误，不淘汰任何已经存在的redis键</p>
<p>allkeys-lru：所有的键使用lru算法进行淘汰</p>
<p>volatile-lru：有过期时间的使用lru算法进行淘汰</p>
<p>allkeys-random：随机删除redis键</p>
<p>volatile-random：随机删除有过期时间的redis键</p>
<p>volatile-ttl：删除快过期的redis键</p>
<p>volatile-lfu：根据lfu算法从有过期时间的键删除</p>
<p>allkeys-lfu：根据lfu算法从所有键删除</p>
<h6 id="流量削峰-："><a href="#流量削峰-：" class="headerlink" title="流量削峰 ："></a>流量削峰 ：</h6><p>系统如果遇到请求流量瞬间猛增，有可能会将系统压垮。有了消息队列可以将请求缓存起来，分散到很长的一段时间处理，这样可以大大提高系统的稳定性和用户体验.</p>
<p>为了保证系统的稳定性，如果系统负载超过阈值，就会阻止用户请求，这会影响用户体验，而如果使用消息队列将用户请求缓存起来，等待系统处理完毕后通知用户下单完毕，这样比不能下单体验要好.</p>
<h4 id="支付"><a href="#支付" class="headerlink" title="支付"></a>支付</h4><figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs">订单支付： 创建好了订单 会展示一个订单创建成功的页面，在这个页面上 会有一个立即支付的按钮，点击立即支付会跳转不同的支付页面， 我们网站 目前主要支持微信 支付宝 易宝 其他网银， 我主要负责 支付宝支付的接口开发。<br>异步订单状态修改： 分布式事务<br>订单支付成功之后， 需要在支付结果的异步通知方法中<br></code></pre></td></tr></table></figure>

<h4 id="分布式事务"><a href="#分布式事务" class="headerlink" title="分布式事务"></a>分布式事务</h4><figure class="highlight ada"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs ada">我们项目中的分布式事务统一使用alibaba的seata框架进行处理的，它支持多种模式的事务处理，我们主要使用的是它里面的<span class="hljs-keyword">AT</span>模式。 具体使用只需要在事务的发起方法中添加@GlobalTrasactional 注解即可。<br></code></pre></td></tr></table></figure>

<h4 id="短信验证码"><a href="#短信验证码" class="headerlink" title="短信验证码"></a>短信验证码</h4><figure class="highlight routeros"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs routeros">首先,我们会判断一下手机号码是否规范,这里,我们会写一个判断手机是否正确的正则工具类,然后调用工具类来判断手机号是否正确,这里,如果手机号不正确的话,直接返回到前台,并把错误信息告诉用户如果手机号正确的话,然后我们调用发送验证码的方法,发送验证码<br>在发送验证码的时候,首先,我们会生成一个6位数的验证码,在生成验证码的时候我们没有用到直接用随机数<span class="hljs-number">*6</span>位来生成验证码,因为这样我们的验证码会有缺失.这里我们公司提供了一个内部的算法，在生成验证码后,我们还要把验证码的存入到redis里,为添加时验证验证码是否正确<br></code></pre></td></tr></table></figure>

<h4 id="验证码生成规则"><a href="#验证码生成规则" class="headerlink" title="验证码生成规则"></a>验证码生成规则</h4><figure class="highlight angelscript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs angelscript">里我们自己写了一个生成验证码的随机数,首先呢我会定义一个<span class="hljs-number">0</span><span class="hljs-number">-9</span>字符串,然后呢在定义一个<span class="hljs-built_in">string</span>Buﬀer对象,<br>和随机数的对象这样我们用循环<span class="hljs-built_in">string</span>Buﬀer这个对象进行操作追加,循环次数呢是<span class="hljs-number">6</span>，追加的对象是用随机对象生成一个<span class="hljs-number">0</span>~<span class="hljs-number">10</span>的下标,用下标来获取定义<span class="hljs-number">0</span>~<span class="hljs-number">9</span>那个字符串中的一个字符，然后把这个追加后的对象toString后返回,这样我们验证码就写好啦<br></code></pre></td></tr></table></figure>

<h4 id="阿里云短信SDK"><a href="#阿里云短信SDK" class="headerlink" title="阿里云短信SDK"></a>阿里云短信SDK</h4><figure class="highlight stylus"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs stylus">公网接入地址      dysmsapi<span class="hljs-selector-class">.aliyuncs</span>.com（华东<span class="hljs-number">1</span> 金融云）<br>手机号            PhoneNumbers（自己传）<br>签名名称          SignName（自己设置的短信验证码名称）<br>模板<span class="hljs-selector-tag">code</span>          TemplateCode（阿里云生成  SMS_268486001  ）<br>注意   <br>	验证码使用JSON格式传递<br></code></pre></td></tr></table></figure>



<h4 id="接口防刷"><a href="#接口防刷" class="headerlink" title="接口防刷"></a>接口防刷</h4><figure class="highlight armasm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs armasm"><span class="hljs-number">1</span>，拦截规则是限制<span class="hljs-built_in">IP</span>在规定实际内访问的次数。<br><span class="hljs-number">2</span>，项目中使用自定义注解和反射机制做了一个通用的拦截器。<br><span class="hljs-number">3</span>，使用redis记录每个请求<span class="hljs-built_in">ip</span>的第一次访问时间，及累计次数，如果达到上限则不予许通过，提示访问频繁。<br></code></pre></td></tr></table></figure>

<h4 id="商户没有收到支付结果的通知问题"><a href="#商户没有收到支付结果的通知问题" class="headerlink" title="商户没有收到支付结果的通知问题?"></a>商户<strong>没有收到支付结果的通知问题?</strong></h4><figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs">用户在我们的支付成功，支付成功一定收到支付宝的通知。在这个通知之前 是会回调我们提供的接口。支付宝回调的过程中，没有调通我们的支付接口。 【支付平台不健壮，就是服务不稳定导致的】 用户马上投诉，解决办法，客服后台，根据用户提供的订单，查询支付的信息（这个查询我们是直接调用 支付包接口提供的查询功能进行查询的，如果存在的话就是我们网站的问题，这我们的代码会修改订单的状态）<br>〇 当然我们还有一个对账系统，这个对账体统只要做的是，将我们支付平台的订单到第三方支付进行查询 ，看是否有支付记录。<br></code></pre></td></tr></table></figure>

<h4 id="订单失效"><a href="#订单失效" class="headerlink" title="订单失效"></a>订单失效</h4><figure class="highlight tap"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs tap">定时任务处理<br>用户下订单后先生成订单信息，然后将该订单加入到定时任务中（30分钟后执行），当到达指定时间后检查订单状态，如果未支付则标识该订单失效。<br>定时去轮询数据库/缓存，看订单的状态。这种方式的问题很明显，当集群部署服务器的时候需要做分布式锁进行协调，而且实时性不高，对数据库会产生压力<br><br>MQ死信队列<br>当下单后我们把订单信息发送到 MQ 的延时队列中，并设置<span class="hljs-number"> 30 </span>分钟过期，30 分钟以后延时队列的数据在转发到死信队列中去，然后我们从死信队列中获取订单信息，并判断它的支付状态，如果已经支付，不做任何处理，如果未支付，则取消订单。<br></code></pre></td></tr></table></figure>

<h4 id="订单重复提交"><a href="#订单重复提交" class="headerlink" title="订单重复提交"></a>订单重复提交</h4><figure class="highlight awk"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs awk"><span class="hljs-number">1</span>、前端拦截<br>前端拦截是指通过 HTML 页面来拦截重复请求，比如在用户点击完“提交”按钮后，我们可以把按钮设置为不可用或者隐藏状态。<br><span class="hljs-number">2</span>、给用户跳转别的页面<br><span class="hljs-number">3</span>、<br>https:<span class="hljs-regexp">//</span>blog.csdn.net<span class="hljs-regexp">/zhangleiyes123/</span>article<span class="hljs-regexp">/details/</span><span class="hljs-number">126345928</span><br></code></pre></td></tr></table></figure>

<h4 id="Token效验"><a href="#Token效验" class="headerlink" title="Token效验"></a>Token效验</h4><p>获取请求的URL 判断请求的URL中是否包含&#x2F;login&#x2F;in ， 然后放行然后获取请求头信息的token效验token是否存在 不存在则返回错误信息 然后让用户继续登录try{解析token中的内容  效验token是否在有效期if判断  如果为0 || -1 则说明该token在有效期向header中设置userid   重新设置token到header中   然后重新构建请求头信息catch{向客户端返回错误信息}最后放行</p>
<h4 id="支付流程"><a href="#支付流程" class="headerlink" title="支付流程"></a>支付流程</h4><p><img src="/xm/image-20230504115659410.png" srcset="/img/loading.gif" lazyload alt="image-20230504115659410"></p>
<p><a target="_blank" rel="noopener" href="https://blog.csdn.net/Lengwenin/article/details/104409790">JAVA对接支付宝支付(超详细，一看就懂)_Mr丶冷文的博客-CSDN博客</a></p>
<h5 id="支付宝支付常见问题"><a href="#支付宝支付常见问题" class="headerlink" title="支付宝支付常见问题"></a>支付宝支付常见问题</h5><p>1、付款金额和应付金额是否一致，（比如：扫描的支付二维码，和显示的应支付金额是否一致）。支付还是要走整个支付流程才行，从确认订单到最后的支付成功，任何一步都有可能有问题。</p>
<p>2、同一种支付方式，不同的支付入口(比如：如下图所示，支付宝有两个支付入口。即可通过扫描二维码支付，也可以通过支付宝网页支付。在测试过程中，两个入口都要覆盖到。</p>
<p>3、支付成功后，产品购买是否成功</p>
<p>比如会员服务产品，购买后会员到期时间是否正常延迟；比如购买商品，支付成功后，订单状态是否更改，商品种类和数量是否正确等等</p>
<p>支付流程测试点</p>
<p>2.调起订单后，取消订单</p>
<p>3.支付中断后，继续支付</p>
<p>4.支付中断后结束支付</p>
<p>5.单笔订单单笔支付</p>
<h5 id="支付宝支付坑点"><a href="#支付宝支付坑点" class="headerlink" title="支付宝支付坑点"></a>支付宝支付坑点</h5><figure class="highlight ceylon"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs ceylon">坑一：页面显示的应付金额通过接口vip.product返回了，前端显示出来应付金额。但是，支付的二维码是通过接口vip.getPayUrl这个接口返回的，结果二维码扫出来的值和显示的应付金额不一样呀！！！最后问题是在于，vip.getPayUrl中取的是服务器缓存，导致二维码显示的金额跟前端展示的应付金额不一致。所以测试支付还是要走整个支付流程才行，从确认订单到最后的支付成功，任何一步都有可能有问题。<br><br>坑二：通过支付宝网站支付，支付成功后，页面没有跳转回原服务套餐网页。最后的原因是服务配置的<span class="hljs-keyword">return</span><span class="hljs-number">_u</span>rl不正确，导致支付后，没有跳回原页面。如果测试用例覆盖不到这种场景，那么将会造成非常严重的线上事故<br></code></pre></td></tr></table></figure>

<h2 id="债券"><a href="#债券" class="headerlink" title="债券"></a>债券</h2><h3 id="专业术语"><a href="#专业术语" class="headerlink" title="专业术语"></a>专业术语</h3><p>在债券项目中，有一些专业的名词和术语，下面是一些常用的专业名词及其解释：</p>
<ol>
<li>债券（Bond）：指借款人向借款人发行的一种证券，它是一种固定收益证券，具有固定的利率和到期日。</li>
<li>发行人（Issuer）：指发行债券的实体或机构，可以是政府、公司或其他组织。</li>
<li>债券持有人（Bondholder）：指持有债券的个人或机构。</li>
<li>期限（Maturity）：指债券的到期日，债券到期后发行人需要偿还本金和利息。</li>
<li>利率（Interest Rate）：指债券的固定利率，表示持有人持有债券期间每年可以获得的利息。</li>
<li>面值（Face Value）：指债券的发行价值，也称为票面价值，是债券在到期时要偿还的本金。</li>
<li>信用评级（Credit Rating）：指对发行人信用状况的评估，通常由专业评级机构进行评估，评级结果越高，表示发行人信用状况越好，风险越低。</li>
<li>债券评级（Bond Rating）：指对债券的评估，评估结果通常和发行人的信用评级密切相关。</li>
<li>债券市场（Bond Market）：指交易债券的市场，包括公开市场和交易场所，是债券投资者和发行人之间的主要交易场所。</li>
<li>债券交易（Bond Trading）：指在债券市场上进行的买卖债券的交易，交易的价格通常取决于债券的利率、期限、发行人信用评级等因素。</li>
</ol>
<p>以上是一些常用的债券项目中的专业名词及其解释，可以帮助您更好地理解债券市场和债券交易。</p>
<h3 id="债券项目的订单业务及流程"><a href="#债券项目的订单业务及流程" class="headerlink" title="债券项目的订单业务及流程"></a>债券项目的订单业务及流程</h3><p>债券项目的订单业务和流程与一般的订单处理模块略有不同，下面是债券项目的订单业务和流程：</p>
<ol>
<li>订单录入：债券投资者提交订单请求，订单处理员将订单信息录入系统中，包括债券种类、数量、面值、发行价等信息。</li>
<li>订单验证：订单处理员会对订单信息进行验证，包括债券种类、发行人信息、面值、发行价等信息的准确性和完整性。如果订单信息有误，订单处理员需要及时通知投资者进行修改。</li>
<li>订单确认：发行人确认债券发行计划后，订单处理员需要确认订单是否可以被执行。如果订单可以被执行，订单处理员需要将订单状态从“待确认”修改为“已确认”。</li>
<li>订单支付：投资者需要向发行人支付债券款项，订单处理员需要确认债券款项的收到，并更新订单状态为“已支付”。</li>
<li>债券发行：发行人按照发行计划进行债券发行，并按照订单要求配送债券给投资者。</li>
<li>债券交割：发行人需要确认投资者收到债券，并与投资者进行债券交割。</li>
<li>订单结算：订单处理员需要对订单进行结算，并将订单信息传递给财务部门进行核算。</li>
<li>订单完成：订单处理员需要跟踪订单整个生命周期，确保订单已经完成并更新订单状态为“已完成”。</li>
</ol>
<p>总之，债券项目的订单业务和流程包括订单录入、订单验证、订单确认、订单支付、债券发行、债券交割、订单结算和订单完成等环节。其目的是确保债券交易的准确性、及时性和完整性，提高交易的效率和投资者的满意度。</p>
<h3 id="年度发行计划"><a href="#年度发行计划" class="headerlink" title="年度发行计划"></a>年度发行计划</h3><p>年度发行计划是中国农业发展银行对某一年度制定的债券总体的、经人民银行审批的发行计划。发行计划生效后，对年度内债券发行业务进行预警和控制。</p>
<p>发行计划按分为临时计划、年度计划、追加计划。临时计划在上一年度年底前制定完成，并经人行审批后，在年初生效；年度计划在年初制定并经人行审批后，替代临时计划并生效；年中发行债券前若发行额度不够，可制定追加计划，经人行审批后，追加年度发行总额度。</p>
<h3 id="发行方案流程图"><a href="#发行方案流程图" class="headerlink" title="发行方案流程图"></a>发行方案流程图</h3><p><img src="/xm/image-20230505092332904.png" srcset="/img/loading.gif" lazyload alt="image-20230505092332904"></p>
<h3 id="普通债券发行流程图"><a href="#普通债券发行流程图" class="headerlink" title="普通债券发行流程图"></a>普通债券发行流程图</h3><p><img src="/xm/clip_image002.jpg" srcset="/img/loading.gif" lazyload alt="img"></p>
<h3 id="项目责任描述："><a href="#项目责任描述：" class="headerlink" title="项目责任描述："></a>项目责任描述：</h3><h4 id="1、债券项目中的表结构设计与逻辑"><a href="#1、债券项目中的表结构设计与逻辑" class="headerlink" title="1、债券项目中的表结构设计与逻辑"></a>1、债券项目中的表结构设计与逻辑</h4><p>在债券项目中，通常需要设计数据库表结构来存储和管理数据。以下是一个可能的债券项目中的表结构设计和逻辑：</p>
<ol>
<li>债券表（Bond Table）：用于存储债券的基本信息，包括债券代码、债券名称、发行人、期限、利率、面值等字段。</li>
<li>债券持有人表（Bondholder Table）：用于存储债券持有人的基本信息，包括持有人姓名、联系方式、证券账户等字段。</li>
<li>订单表（Order Table）：用于存储债券交易订单信息，包括订单编号、买卖方向、成交价格、成交数量等字段。</li>
<li>交易记录表（Transaction Table）：用于存储债券交易记录，包括交易时间、成交价格、成交数量、交易费用等字段。</li>
<li>发行人表（Issuer Table）：用于存储债券发行人的基本信息，包括发行人名称、地址、联系方式等字段。</li>
<li>债券评级表（Bond Rating Table）：用于存储债券评级信息，包括评级机构、评级结果、评级时间等字段。</li>
<li>信用评级表（Credit Rating Table）：用于存储发行人的信用评级信息，包括评级机构、评级结果、评级时间等字段。</li>
</ol>
<h4 id="2、负责系统维护的年度发行计划信息："><a href="#2、负责系统维护的年度发行计划信息：" class="headerlink" title="2、负责系统维护的年度发行计划信息："></a>2、负责系统维护的年度发行计划信息：</h4><p>负责维护债券交易系统的机构或个人会发布一份年度发行计划，该计划包括他们计划在一年内发行的债券种类、数量、发行时间、发行价格等详细信息，供投资者和其他利益相关方参考。年度发行计划信息对于债券市场和投资者非常重要，因为它们可以根据这些信息做出更加明智的投资决策，并在债券市场中寻找更好的投资机会。同时，年度发行计划信息也可以帮助债券发行人更好地规划和管理他们的债券发行业务，确保债券发行的准确性和及时性。</p>
<h4 id="3、实现电子台账，支持各项业务数据的存储、提取、统计和归纳，并可按需生成数据报表；"><a href="#3、实现电子台账，支持各项业务数据的存储、提取、统计和归纳，并可按需生成数据报表；" class="headerlink" title="3、实现电子台账，支持各项业务数据的存储、提取、统计和归纳，并可按需生成数据报表；"></a>3、实现电子台账，支持各项业务数据的存储、提取、统计和归纳，并可按需生成数据报表；</h4><ol>
<li>电子台账的建立：债券项目需要建立电子台账，用于存储各项业务数据，如债券信息、持有人信息、交易订单信息、交易记录信息等。</li>
<li>数据存储：将各项业务数据存储到电子台账中，确保数据的完整性和一致性。</li>
<li>数据提取：根据业务需求，从电子台账中提取相应的数据，如查询债券信息、持有人信息、交易订单信息等。</li>
<li>数据统计和归纳：对提取的数据进行统计和归纳，如统计某段时间内的交易订单数量、归纳持有人的投资偏好等。</li>
<li>数据报表生成：根据业务需求，按需生成数据报表，如生成债券交易报表、持有人投资报表等。</li>
<li>数据报表分析：对生成的数据报表进行分析，以便更好地了解市场情况和投资机会，提高决策能力。</li>
<li>数据更新：随着业务的进行，需要及时更新电子台账中的数据，以确保数据的及时性和准确性。</li>
</ol>
<h4 id="4、实现业务提醒，具体包括对各业务环节，要求期限内及时完成的各-类业务，按照不同事项展现和提醒办理"><a href="#4、实现业务提醒，具体包括对各业务环节，要求期限内及时完成的各-类业务，按照不同事项展现和提醒办理" class="headerlink" title="4、实现业务提醒，具体包括对各业务环节，要求期限内及时完成的各 类业务，按照不同事项展现和提醒办理"></a>4、实现业务提醒，具体包括对各业务环节，要求期限内及时完成的各 类业务，按照不同事项展现和提醒办理</h4><ol>
<li>确定需要提醒的业务环节和时间要求。根据债券业务的流程和规定，确定需要提醒的业务环节，以及每个环节要求的完成时间。</li>
<li>设计提醒方式和方式的展现。根据需要提醒的业务环节和时间要求，确定提醒方式，可以通过邮件、短信、系统弹窗等方式提醒用户，同时也需要设计提醒方式的展现，如提供清晰明了的提醒内容、提醒的时间等。</li>
<li>开发提醒模块。根据设计的提醒方式和展现方式，开发业务提醒模块，实现提醒功能。该模块可以与业务流程管理模块进行集成，根据流程进展和时间要求，触发相应的提醒。</li>
<li>对不同的业务环节进行不同的提醒。不同的业务环节要求完成的时间不同，因此需要根据不同的时间要求和业务环节，设置不同的提醒方式和时间，保证用户能够在规定的时间内完成相应的业务，避免超时和风险。</li>
<li>监控提醒效果。根据提醒模块的使用情况和提醒效果，及时调整和优化提醒方式和展现方式，保证提醒功能的有效性和及时性。同时，根据用户的反馈和需求，进一步完善和优化提醒功能，提高用户体验和业务效率。</li>
</ol>
<h3 id="可能遇到的问题"><a href="#可能遇到的问题" class="headerlink" title="可能遇到的问题"></a>可能遇到的问题</h3><h4 id="债券发行失败怎么处理"><a href="#债券发行失败怎么处理" class="headerlink" title="债券发行失败怎么处理"></a>债券发行失败怎么处理</h4><p>债券发行失败通常会涉及到多个方面，包括法律、财务、风险等各方面因素。以下是一些处理失败债券发行的可能措施：</p>
<ol>
<li>原因分析：首先需要对债券发行失败的原因进行详细分析，包括市场因素、政策因素、财务因素等，从而针对性地采取应对措施。</li>
<li>延期发行：如果债券发行失败的原因是市场不利、投资者需求不足等因素导致，可以考虑延期发行，等待市场情况好转。</li>
<li>重组方案：如果债券发行失败的原因是公司财务状况不佳或债券条件不合理等因素导致，可以考虑重组方案，重新制定债券条款，以更好地吸引投资者。</li>
<li>补充信息：如果债券发行失败的原因是信息不足或不充分导致，可以考虑提供更详细、更充分的信息，以增强投资者对债券的信心。</li>
<li>取消发行：如果以上措施均无法解决债券发行失败的问题，可以考虑取消债券发行，同时及时向投资者和相关方面进行说明和赔偿。</li>
</ol>
<p>需要注意的是，针对债券发行失败的具体处理措施应该根据具体情况进行综合分析和决策，同时需要遵守相关法律法规，确保债券市场秩序的正常运行。</p>
<h4 id="额度控制规则"><a href="#额度控制规则" class="headerlink" title="额度控制规则"></a>额度控制规则</h4><p>额度控制规则是指在一定时间内对某个业务或者某个客户的总量或单笔量进行限制或控制的规则，目的是为了避免过度的风险暴露和损失，保障业务正常运营和客户的利益。</p>
<p>在金融行业中，额度控制是一项非常重要的风险管理措施，常见的控制方式包括：</p>
<ol>
<li>总量控制：对某个客户或者某个业务在一定时间内的总量进行控制，以避免风险集中。</li>
<li>单笔控制：对某个业务或者某个客户单笔的最大金额进行控制，以避免因单笔金额过大而导致的风险。</li>
<li>风险度控制：对某个客户或者某个业务的风险度进行控制，一般采用风险度评估模型来计算风险度，并制定相应的控制规则。</li>
<li>时效控制：对某个业务或者某个客户的授信额度进行时效性控制，以确保在一定时间内使用授信额度，以免授信额度长时间闲置。</li>
</ol>
<p>额度控制规则需要根据不同的业务、客户和产品类型进行个性化制定，综合考虑多个因素，如客户信用状况、风险度、市场环境等。</p>
<h4 id="债券列表还未发行时，列表信息那里来"><a href="#债券列表还未发行时，列表信息那里来" class="headerlink" title="债券列表还未发行时，列表信息那里来"></a>债券列表还未发行时，列表信息那里来</h4><p>在债券还未发行前，一般会提前公布债券的发行计划，并在相关的<strong>金融媒体、证券交易所</strong>或其他<strong>合法途径上</strong>公示相关信息。债券发行计划包括<strong>债券发行的时间、募集规模、利率、期限、发行方式等</strong>基本信息。投资者可以通过这些信息初步了解债券的基本情况，并根据自己的需求决定是否参与投资。在债券发行前，通常会有<strong>相关的机构</strong>对债券的信用等级进行评级，并公示评级结果。这些信息都可以作为债券列表信息的来源。</p>
<h4 id="债券计划发行入参和出参"><a href="#债券计划发行入参和出参" class="headerlink" title="债券计划发行入参和出参"></a>债券计划发行入参和出参</h4><p>债券计划发行的入参和出参可能会因公司和项目的不同而有所差异，以下是一些可能涉及到的常见参数：</p>
<p><strong>入参：</strong></p>
<ul>
<li>债券募集规模：即计划募集的债券总金额。</li>
<li>债券发行方式：包括公开发行和私募发行等不同方式。</li>
<li>债券期限：债券的存续期限。</li>
<li>债券利率：债券的年利率或者票面利率。</li>
<li>债券还款方式：包括到期一次还本付息、分期偿还本息等不同方式。</li>
<li>发行费用：包括承销费、保荐费、评级费等。</li>
</ul>
<p><strong>出参：</strong></p>
<ul>
<li>债券募集结果：即实际募集到的债券金额。</li>
<li>债券发行价格：债券的发行价格，即每张债券的售价。</li>
<li>发行日期：债券的发行日期。</li>
<li>到期日期：债券的到期日期。</li>
<li>债券评级：债券的信用评级。</li>
<li>发行人信息：包括公司名称、注册资本、所在地等。</li>
<li>债券承销商信息：包括承销商名称、承销费等。</li>
</ul>
<h4 id="债券种类有哪些"><a href="#债券种类有哪些" class="headerlink" title="债券种类有哪些"></a>债券种类有哪些</h4><p>债券是一种固定收益证券，按照不同的分类标准可以分为多种类型，常见的债券种类包括：</p>
<p><strong>1.政府债券：</strong>由政府发行，通常作为政府筹集资金的一种方式。政府债券通常分为国库券、地方政府债券、政策性金融债券等。</p>
<p><strong>2.公司债券</strong>：由公司发行，通常作为公司筹集资金的一种方式。公司债券通常分为普通债券、可转换债券、可交换债券、浮动利率债券等。</p>
<p><strong>3.金融债券：</strong>由金融机构发行，通常作为金融机构筹集资金的一种方式。金融债券通常分为银行债券、证券公司债券、保险公司债券等。</p>
<p><strong>4.抵押债券：</strong>以抵押品作为担保发行的债券。抵押债券通常分为房地产抵押债券、质押债券、应收账款抵押债券等。</p>
<p><strong>5.零息债券</strong>：无息债券，债券发行人不支付任何利息，但在到期时按面值偿还债券本金。零息债券通常作为某些金融交易的一种工具使用，例如利率互换交易。</p>
<h4 id="债券类型"><a href="#债券类型" class="headerlink" title="债券类型"></a>债券类型</h4><p>可以分为<strong>公募债</strong>和<strong>私募债</strong>两种。</p>
<p>公募债是指通过证券交易所、银行间市场等公开市场进行发行和交易的债券，可以供广大投资者参与。</p>
<p>私募债是指直接面向有限的几位或者一位投资者发行的债券，不在公开市场上进行交易，适用于一些特定的投资者。</p>
<p>因此，如果一只债券是私募债，那么它通常不会在公开市场上交易，而是直接向符合条件的特定投资者发行。</p>
<h4 id="债券怎么发行的"><a href="#债券怎么发行的" class="headerlink" title="债券怎么发行的"></a>债券怎么发行的</h4><p>债券的发行主要是由公司、政府和金融机构等发行主体发行。一般来说，债券的发行过程如下：</p>
<ol>
<li>确定发行计划：发行主体首先要确定债券的发行规模、发行时间和发行方式等，这需要根据市场需求、融资需求和法律法规等因素综合考虑。</li>
<li>确定债券种类和期限：发行主体要根据自身的融资需求、投资者的偏好和市场状况等因素，选择适合的债券种类和期限，如企业债、可转债、地方政府债等。</li>
<li>资产评估和发行方案设计：为了确保债券的发行能够成功，发行主体需要进行资产评估，确定可供抵押的资产和可用的担保方式等。同时，还要制定发行方案，包括发行方式、发行价格、发行数量等。</li>
<li>发布招募说明书：发行主体需要发布招募说明书，向投资者公开债券的发行信息，包括债券的种类、期限、利率、风险评级等。</li>
<li>安排承销商：发行主体需要安排承销商，对债券进行销售和推广，吸引投资者购买债券。</li>
<li>开始发行：发行主体需要在约定的时间和地点开始发行债券。在债券发行期间，投资者可以通过承销商或者其他渠道购买债券。</li>
<li>偿还本息：债券到期后，发行主体需要按照约定的利率和期限偿还债券本金和利息。</li>
</ol>
<h1 id="项目后续"><a href="#项目后续" class="headerlink" title="项目后续"></a>项目后续</h1><h2 id="项目部署上线"><a href="#项目部署上线" class="headerlink" title="项目部署上线"></a>项目部署上线</h2><figure class="highlight mipsasm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs mipsasm">具体几台服务器，我不太清楚，有专门的运维负责，不过部署流程采用的是持续集成的方式，<br>开发将代码上传到git, git会触发持续集成<span class="hljs-keyword">jenkins进行任务构建（webhooks） </span>构建是基于docker构建镜像，并推送到指定镜像中心，然后在触发rancher进行服务部署，这种持续集成的方案。<br><br>不过大概的部署架构我了解一些，接入层采用nginx 接收所有用户请求，在nginx中部署前端的静态资源，后台微服务网关的访问也是通过nginx, 配置的反向代理。 微服务架构中部署nacos作为注册中心，配置中心。 然后每一个微服务也都是构建成docker镜像，通过docker来部署的。<br></code></pre></td></tr></table></figure>

<h2 id="处理Bug"><a href="#处理Bug" class="headerlink" title="处理Bug"></a>处理Bug</h2><figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs">首先通过断点调试找出具体问题在那里 之后便自己进行解决 自己解决不了通过谷歌、掘金 csdn来解决都不行就与同事共同探讨<br></code></pre></td></tr></table></figure>

<h2 id="项目压测"><a href="#项目压测" class="headerlink" title="项目压测"></a>项目压测</h2><figure class="highlight gcode"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs gcode">使用Jmeter进行压力测试<br><span class="hljs-number">1.</span>接口最大响应时间<span class="hljs-comment">(时间太长，客户要发彪)</span><br><span class="hljs-number">3.</span>CPU<span class="hljs-comment">(CPU爆顶，影响运维，和运维人员商定高峰CPU值)</span><br><span class="hljs-number">4.</span>JVM<span class="hljs-comment">(JVM溢出还想啥啊，优化程序或者考虑加机器分流)</span><br><span class="hljs-number">5.</span>mysql的连接数<br></code></pre></td></tr></table></figure>

<h2 id="你用的SpringBoot几"><a href="#你用的SpringBoot几" class="headerlink" title="你用的SpringBoot几"></a>你用的SpringBoot几</h2><figure class="highlight dns"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs dns">使用的是<span class="hljs-number">2</span>.<span class="hljs-number">2</span>.<span class="hljs-number">0</span>.<br>它跟低版本的区别了解吗？<br><span class="hljs-number">1.0和2.0</span>整体差别不是很大。代码上没有改动，改动的地方都是配置依赖的版本。比如<span class="hljs-number">2</span>.<span class="hljs-number">0</span>最低支持的tomcat是<span class="hljs-number">8</span>.<span class="hljs-number">0</span>，必须支持jdk等。肯定是越往后功能越强大。<br></code></pre></td></tr></table></figure>

<p>mysql 5.7</p>
<p>redis 2.5.4</p>

                
              </div>
            
            <hr/>
            <div>
              <div class="post-metas my-3">
  
  
    <div class="post-meta">
      <i class="iconfont icon-tags"></i>
      
        <a href="/tags/%E7%AE%80%E5%8E%86/">#简历</a>
      
    </div>
  
</div>


              
  

  <div class="license-box my-3">
    <div class="license-title">
      <div>项目</div>
      <div>http://example.com/2023/05/10/xm/</div>
    </div>
    <div class="license-meta">
      
        <div class="license-meta-item">
          <div>Author</div>
          <div>an-qiao</div>
        </div>
      
      
        <div class="license-meta-item license-meta-date">
          <div>Posted on</div>
          <div>2023-05-We</div>
        </div>
      
      
      
        <div class="license-meta-item">
          <div>Licensed under</div>
          <div>
            
              
              
                <a target="_blank" href="https://creativecommons.org/licenses/by/4.0/">
                  <span class="hint--top hint--rounded" aria-label="BY - Attribution">
                    <i class="iconfont icon-by"></i>
                  </span>
                </a>
              
            
          </div>
        </div>
      
    </div>
    <div class="license-icon iconfont"></div>
  </div>



              
                <div class="post-prevnext my-3">
                  <article class="post-prev col-6">
                    
                    
                      <a href="/2023/05/10/jishu/" title="技术">
                        <i class="iconfont icon-arrowleft"></i>
                        <span class="hidden-mobile">技术</span>
                        <span class="visible-mobile">Previous</span>
                      </a>
                    
                  </article>
                  <article class="post-next col-6">
                    
                    
                      <a href="/2023/04/09/one1/" title="第一篇博客">
                        <span class="hidden-mobile">第一篇博客</span>
                        <span class="visible-mobile">Next</span>
                        <i class="iconfont icon-arrowright"></i>
                      </a>
                    
                  </article>
                </div>
              
            </div>

            
  
  
    <article id="comments" lazyload>
      
  <div id="valine"></div>
  <script type="text/javascript">
    Fluid.utils.loadComments('#valine', function() {
      Fluid.utils.createScript('https://lib.baomitu.com/valine/1.5.1/Valine.min.js', function() {
        var options = Object.assign(
          {"enable":true,"appId":"sY559Z1yXZXJq3W7dv61pwQx-gzGzoHsz","appKey":"trFchyq32HKcrEtBcZ6Bx51y","path":"window.location.pathname","placeholder":"期待与您的交流","avatar":"retro","meta":["nick","mail","link"],"requiredFields":[],"pageSize":10,"lang":"zh-CN","highlight":false,"recordIP":false,"serverURLs":"","emojiCDN":null,"emojiMaps":null,"enableQQ":false},
          {
            el: "#valine",
            path: window.location.pathname
          }
        )
        new Valine(options);
        Fluid.utils.waitElementVisible('#valine .vcontent', () => {
          var imgSelector = '#valine .vcontent img:not(.vemoji)';
          Fluid.plugins.imageCaption(imgSelector);
          Fluid.plugins.fancyBox(imgSelector);
        })
      });
    });
  </script>
  <noscript>Please enable JavaScript to view the comments</noscript>


    </article>
  


          </article>
        </div>
      </div>
    </div>

    <div class="side-col d-none d-lg-block col-lg-2">
      

    </div>
  </div>
</div>





  



  



  



  



  







    

    
      <a id="scroll-top-button" aria-label="TOP" href="#" role="button">
        <i class="iconfont icon-arrowup" aria-hidden="true"></i>
      </a>
    

    
      <div class="modal fade" id="modalSearch" tabindex="-1" role="dialog" aria-labelledby="ModalLabel"
     aria-hidden="true">
  <div class="modal-dialog modal-dialog-scrollable modal-lg" role="document">
    <div class="modal-content">
      <div class="modal-header text-center">
        <h4 class="modal-title w-100 font-weight-bold">Search</h4>
        <button type="button" id="local-search-close" class="close" data-dismiss="modal" aria-label="Close">
          <span aria-hidden="true">&times;</span>
        </button>
      </div>
      <div class="modal-body mx-3">
        <div class="md-form mb-5">
          <input type="text" id="local-search-input" class="form-control validate">
          <label data-error="x" data-success="v" for="local-search-input">Keyword</label>
        </div>
        <div class="list-group" id="local-search-result"></div>
      </div>
    </div>
  </div>
</div>

    

    
  </main>

  <footer>
    <div class="footer-inner">
  
    <div class="footer-content">
       <a href="https://hexo.io" target="_blank" rel="nofollow noopener"><span>Hexo</span></a> <i class="iconfont icon-love"></i> <a href="https://github.com/fluid-dev/hexo-theme-fluid" target="_blank" rel="nofollow noopener"><span>Fluid</span></a> 
    </div>
  
  
    <div class="statistics">
  
  

  
    
      <span id="busuanzi_container_site_pv" style="display: none">
        Views: 
        <span id="busuanzi_value_site_pv"></span>
        
      </span>
    
    
      <span id="busuanzi_container_site_uv" style="display: none">
        Visitors: 
        <span id="busuanzi_value_site_uv"></span>
        
      </span>
    
    
  
</div>

  
  
  
</div>

  </footer>

  <!-- Scripts -->
  
  <script  src="https://lib.baomitu.com/nprogress/0.2.0/nprogress.min.js" ></script>
  <link  rel="stylesheet" href="https://lib.baomitu.com/nprogress/0.2.0/nprogress.min.css" />

  <script>
    NProgress.configure({"showSpinner":false,"trickleSpeed":100})
    NProgress.start()
    window.addEventListener('load', function() {
      NProgress.done();
    })
  </script>


<script  src="https://lib.baomitu.com/jquery/3.6.0/jquery.min.js" ></script>
<script  src="https://lib.baomitu.com/twitter-bootstrap/4.6.1/js/bootstrap.min.js" ></script>
<script  src="/js/events.js" ></script>
<script  src="/js/plugins.js" ></script>


  <script  src="https://lib.baomitu.com/typed.js/2.0.12/typed.min.js" ></script>
  <script>
    (function (window, document) {
      var typing = Fluid.plugins.typing;
      var subtitle = document.getElementById('subtitle');
      if (!subtitle || !typing) {
        return;
      }
      var text = subtitle.getAttribute('data-typed-text');
      
        typing(text);
      
    })(window, document);
  </script>




  
    <script  src="/js/img-lazyload.js" ></script>
  




  
<script>
  Fluid.utils.createScript('https://lib.baomitu.com/tocbot/4.18.2/tocbot.min.js', function() {
    var toc = jQuery('#toc');
    if (toc.length === 0 || !window.tocbot) { return; }
    var boardCtn = jQuery('#board-ctn');
    var boardTop = boardCtn.offset().top;

    window.tocbot.init(Object.assign({
      tocSelector     : '#toc-body',
      contentSelector : '.markdown-body',
      linkClass       : 'tocbot-link',
      activeLinkClass : 'tocbot-active-link',
      listClass       : 'tocbot-list',
      isCollapsedClass: 'tocbot-is-collapsed',
      collapsibleClass: 'tocbot-is-collapsible',
      scrollSmooth    : true,
      includeTitleTags: true,
      headingsOffset  : -boardTop,
    }, CONFIG.toc));
    if (toc.find('.toc-list-item').length > 0) {
      toc.css('visibility', 'visible');
    }

    Fluid.events.registerRefreshCallback(function() {
      if ('tocbot' in window) {
        tocbot.refresh();
        var toc = jQuery('#toc');
        if (toc.length === 0 || !tocbot) {
          return;
        }
        if (toc.find('.toc-list-item').length > 0) {
          toc.css('visibility', 'visible');
        }
      }
    });
  });
</script>


  <script src=https://lib.baomitu.com/clipboard.js/2.0.11/clipboard.min.js></script>

  <script>Fluid.plugins.codeWidget();</script>


  
<script>
  Fluid.utils.createScript('https://lib.baomitu.com/anchor-js/4.3.1/anchor.min.js', function() {
    window.anchors.options = {
      placement: CONFIG.anchorjs.placement,
      visible  : CONFIG.anchorjs.visible
    };
    if (CONFIG.anchorjs.icon) {
      window.anchors.options.icon = CONFIG.anchorjs.icon;
    }
    var el = (CONFIG.anchorjs.element || 'h1,h2,h3,h4,h5,h6').split(',');
    var res = [];
    for (var item of el) {
      res.push('.markdown-body > ' + item.trim());
    }
    if (CONFIG.anchorjs.placement === 'left') {
      window.anchors.options.class = 'anchorjs-link-left';
    }
    window.anchors.add(res.join(', '));

    Fluid.events.registerRefreshCallback(function() {
      if ('anchors' in window) {
        anchors.removeAll();
        var el = (CONFIG.anchorjs.element || 'h1,h2,h3,h4,h5,h6').split(',');
        var res = [];
        for (var item of el) {
          res.push('.markdown-body > ' + item.trim());
        }
        if (CONFIG.anchorjs.placement === 'left') {
          anchors.options.class = 'anchorjs-link-left';
        }
        anchors.add(res.join(', '));
      }
    });
  });
</script>


  
<script>
  Fluid.utils.createScript('https://lib.baomitu.com/fancybox/3.5.7/jquery.fancybox.min.js', function() {
    Fluid.plugins.fancyBox();
  });
</script>


  <script>Fluid.plugins.imageCaption();</script>

  <script  src="/js/local-search.js" ></script>

  <script defer src="/js/leancloud.js" ></script>

  <script defer src="https://busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js" ></script>





<!-- 主题的启动项，将它保持在最底部 -->
<!-- the boot of the theme, keep it at the bottom -->
<script  src="/js/boot.js" ></script>


  

  <noscript>
    <div class="noscript-warning">Blog works best with JavaScript enabled</div>
  </noscript>
</body>
</html>
